
#include "common.h"
#include "sock.h"
#include "http.h"


static void sock_handle_write(int fd, short flag, void *arg);
static void sock_handle_sendfile(int fd, short flag, void *data);
static void mem_write_state_free(mem_write_state *mws);
static void sendfile_state_free(sendfile_state *ss);

int sock_open(int reuse, int noblock)
{
	int fd = -1;
	if(-1 == (fd = socket(AF_INET, SOCK_STREAM, 0))) {
		error_log("open sockect %d failed\n", fd);
	}
	
	if(reuse)
		sock_reuseaddr(fd);
	if(noblock)
		sock_noblocking_set(fd);
	return fd;
}

void sock_close(int fd)
{
	if(fd < 0)
		return;
	close(fd);
	debug_log(3, "fd %d closed\n", fd);
}

void sock_listen(int fd, int queue)
{
	if(fd < 0) {
		debug_log(1, "fd %d listen failed\n", fd);
		return;
	}

	if(listen(fd, queue) == -1) {
		error_log("listen %d failed\n", fd);
		return;
	}

	debug_log(1, "fd %d listen, backlog %d\n", fd, queue);
}

int sock_bind(int fd, struct sockaddr *addr, int addr_size)
{
	if(fd < 0) {
		debug_log(1, "fd %d < 0\n", fd);
		return -1;
	}
	if(-1 == (bind(fd, addr, addr_size))) {
		error_log("bind %d failed. Exit\n", fd);
		return -1;
	}
	
	debug_log(1, "fd %d bind %012X\n", fd, ((struct sockaddr_in *)addr)->sin_addr);
	return 0;
}

int sock_accept(int fd, struct sockaddr *addr, int size)
{
	if(fd < 0) 
		return -1;

	int c_fd = accept(fd, addr, (socklen_t *)&size);
	if(-1 == c_fd) {
		debug_log(2, "%d accept failed\n", fd);
		return -1;
	}
	
	debug_log(3, "accept %d connection\n", c_fd);
	return c_fd;
}

int error_ignore(int error_no) 
{         
	switch (error_no) {
		case EINPROGRESS:
		case EAGAIN:
		case EINTR: 
#if defined(ERESTART) 
		case ERESTART:
			return 1;
#endif
		return 1;
		default:
			return 0;
	}
	/* NOTREACHED */
}

int sock_noblocking_set(int fd)
{         
	int flag;
	int dummy = 0;

	if(-1 == (flag = fcntl(fd, F_GETFL, dummy))) {
		error_log("fcntl %d failed\n", fd);
		return -1; 
	}   

	if(-1 == (flag = fcntl(fd, F_SETFL, dummy | O_NONBLOCK))) {
		error_log("fcntl %d failed\n", fd);
		return -1;
	}
	debug_log(8, "fd %d noblocking set\n", fd);
	return 0;
}

void sock_write(event_t *ev, int fd, WPF *handler, void *arg, buffer_t *s)
{
	if(!ev) 
		return;
	mem_write_state *mws ;

	mws = mempool_alloc(sizeof(*mws));
	assert(mws);
	mws->buf =  (char *)mempool_dup(s->buf, s->offset);
	mws->size = s->offset;
	mws->offset = 0;
	mws->handler = handler;
	mws->arg = arg;
	mws->ev = ev;
	if( -1 == event_update(ev, SOCK_FLAG_WRITE, mws, sock_handle_write, NULL)) {
		error_log("fd %d event update failed\n", fd);
		return;
	}
	debug_log(9, "offset %d, size %d, buf %p\n", \
					mws->offset, mws->size, mws->buf);
}


static void sock_handle_write(int fd, short flag, void *arg)
{
	assert(arg);

	mem_write_state *mws = arg;
	int sz;
	int sz_need;
	int error = 0;
	
	sz_need = mws->size - mws->offset;
	debug_log(9, "offset %d, size %d, buf %p\n", \
					mws->offset, mws->size, mws->buf);
	if(sz_need == 0) {
		WPF *cb = mws->handler;
		int offset = mws->offset;
		void *arg = mws->arg;
		mem_write_state_free(mws);
		if(cb) {
			cb(fd, error, offset, arg);
		}
		return;
	}
	sz = write(fd, mws->buf + mws->offset, sz_need);
	if(sz == -1) {
		if(error_ignore(errno))
			return;
		error = 1;
		WPF *cb = mws->handler;
		int offset = mws->offset;
		void *arg = mws->arg;
		mem_write_state_free(mws);
		if(cb) {
			cb(fd, error, offset, arg);
		}
		return;
		
	}
	mws->offset += sz;
	if(mws->offset >= mws->size) {
		WPF *cb = mws->handler;
		int offset = mws->offset;
		void *arg = mws->arg;
		mem_write_state_free(mws);
		if(cb) {
			cb(fd, error, offset, arg);
		}
		return;
	}
}

void sock_reuseaddr(int fd)
{
	int on = 1;
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0)
		error_log("FD %d set reuseaddr failed\n", fd);
}

static void mem_write_state_free(mem_write_state *mws)
{
	if(!mws) 
		return;

	event_del(mws->ev);
	safe_free(mws->buf);
	mempool_free(mws);
}

void sock_sendfile(event_t *ev, int in, int out, int offset, int size, WPF *callback, void *data)
{
	sendfile_state *ss = mempool_alloc(sizeof(*ss));
	
	assert(ss);
	ss->in_fd = in;
	ss->out_fd = out; 
	ss->offset = offset; 
	ss->size = size;
	ss->size_send = 0;
	ss->callback = callback;
	ss->data = data;
	ss->ev = ev;
	
	if(event_update(ev, SOCK_FLAG_WRITE, ss, sock_handle_sendfile, NULL) == -1) {
		error_log("fd %d event update failed\n", out);
		return;
	}
}

static void sock_handle_sendfile(int fd, short flag, void *data)
{
	assert(data);
	int error = 0;
	sendfile_state *ss = data;
	int size_need = ss->size - ss->size_send;
	int res;
	off_t size_write = -1;

#if defined(HAVE_SYS_SENDFILE_H) && !defined(HAVE_SYS_UIO_H)
	size_write = sendfile(ss->out_fd, ss->in_fd, (off_t *)&ss->offset, size_need);
	if(size_write == -1) {
		if(error_ignore(errno))  {
			return;
		}
		else {
			error_log("fd %d sendfile failed\n", fd);
			error = 1;
			WPF *handle = ss->callback;
			void *handle_data = ss->data;
			res = ss->size_send;
			sendfile_state_free(ss);
			handle(fd, error, res, handle_data);
			return;
		}
	}
#elif defined(HAVE_SYS_UIO_H) && !defined(HAVE_SYS_SENDFILE_H)
	if(-1 == sendfile(ss->in_fd, ss->out_fd, \
							(off_t )ss->offset, size_need, NULL, &size_write, 0)) {
		if(error_ignore(errno))
			return;
		else {
			error_log("fd %d sendfile failed\n", fd);
			error = 1;
			WPF *handle = ss->callback;
			void *handle_data = ss->data;
			res = ss->size_send;
			sendfile_state_free(ss);
			handle(fd, error, res, handle_data);
			return;
		}
	}
	ss->offset += size_write;
#endif

	ss->size_send += size_write;
	if(ss->size_send < ss->size) {
		return;
	}
	else {
		WPF *handle = ss->callback;
		res = ss->size_send;
		void *handle_data = ss->data;
		sendfile_state_free(ss);
		handle(fd, error, res, handle_data);
	}
}

void sock_cork_set(int fd)
{
	int on = 1; 
#if defined(TCP_CORK) && ! defined(TCP_NOPUSH)	
	if(-1 == setsockopt(fd, IPPROTO_TCP, TCP_CORK, (char *)&on, sizeof(on))) {
		error_log("fd %d cork set failed", fd);
	}
#elif defined(TCP_NOPUSH) && !defined(TCP_CORK)
	if(-1 == setsockopt(fd, IPPROTO_TCP, TCP_NOPUSH, (char *)&on, sizeof(on))) {
		error_log("fd %d cork set failed", fd);
	}
#endif
}

static void sendfile_state_free(sendfile_state *ss)
{
	if(!ss)
		return;

	event_del(ss->ev);
	mempool_free(ss);
}
